package com.shelpe.services.algorithm.autoDrive;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.shelpe.services.algorithm.choose.ChooseHandler;
import com.shelpe.services.algorithm.client.LexiconClient;
import com.shelpe.services.algorithm.client.SimbaProxyClient;
import com.shelpe.services.algorithm.client.SimbaProxyClient.GlobalWordRespone;
import com.shelpe.services.algorithm.client.SinbadClient;
import com.shelpe.services.algorithm.entity.batis.Keyword;
import com.shelpe.services.algorithm.entity.batis.KeywordExample;
import com.shelpe.services.algorithm.entity.batis.RptKeywordExample;
import com.shelpe.services.algorithm.entity.jpa.Adgroup;
import com.shelpe.services.algorithm.entity.jpa.AutoProductQueue;
import com.shelpe.services.algorithm.entity.jpa.Campaign;
import com.shelpe.services.algorithm.entity.mongo.AutoDriveActionLog;
import com.shelpe.services.algorithm.repository.batis.KeywordMapper;
import com.shelpe.services.algorithm.repository.batis.RptKeywordMapper;
import com.shelpe.services.algorithm.repository.jpa.AutoProductQueueRepository;
import com.shelpe.services.algorithm.repository.jpa.CampaignRepository;
import com.shelpe.services.algorithm.repository.mongo.AutoDriveActionLogRepository;
import com.shelpe.services.algorithm.sustain.SustainRule;
import com.shelpe.services.algorithm.utils.AutoDriverUtils;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class AutoDriveSustainRule implements SustainRule<AutoDriveSustainer> {
	@Autowired
	private AutoProductQueueRepository autoProductQueueRepository;
	@Autowired
	private SinbadClient sinbadClient;
	@Autowired
	private LexiconClient lexiconClient;
	@Autowired
	private SimbaProxyClient simbaProxyClient;
	@Autowired
	private RptKeywordMapper rptKeywordMapper;
	@Autowired
	private KeywordMapper keywordMapper;
	@Autowired
	private AutoDriveKeywordSustainRule autoDriveKeywordSustainRule;
	@Autowired
	private CampaignRepository campaignRepository;
	@Autowired
	private DefaultRecommendWordFilter recommendWordFilter;
	@Autowired
	private AutoDriveActionLogRepository autoDriveActionLogRepository;
	@Autowired
	private List<ChooseHandler<Adgroup>> chooseHandlers;
	
	@Override
	public boolean check(AutoDriveSustainer entity) {
		boolean isAuto = entity.getAdgroup().getDriveType() == 1; 
		long days = 0;
		boolean first = false;
		if(entity.getAdgroup().getMaintainedDate() == null){
			first = true;
		}else{
			Date today = DateUtils.truncate(new Date(), Calendar.DATE);
			days = (today.getTime() - entity.getAdgroup().getMaintainedDate().getTime()) / (1000 * 60 * 60 *24);
		}
		AutoProductQueue queue = this.autoProductQueueRepository.findByCampaignIDAndProductID(entity.getAdgroup().getCampaignID(), entity.getAdgroup().getProductID());
		entity.setQueue(queue);
		boolean isInPeroid = false;
		if(entity.getQueue() != null && entity.getQueue().getType() == AutoProductQueue.DriveType.click){
			isInPeroid = (days >= 1);
		}else{
			isInPeroid = (days >= 3);
		}
		boolean res =  isAuto && (isInPeroid || first) && "online".equals(entity.getAdgroup().getOnlineStatus());
		if(!res){
			log.info("exit as: auto - {}, peroid - {}, is first run - {}, status - {}", isAuto, isInPeroid, first, "online".equals(entity.getAdgroup().getOnlineStatus()));
		}
		Campaign campaign = this.campaignRepository.findOne(entity.getAdgroup().getCampaignID());
		boolean campaignRes = campaign != null && "online".equals(campaign.getOnlineStatus());
		if(!campaignRes){
			log.info("exit as: campaigin - {}", campaign);
		}
		
		boolean next = res && campaignRes;
		return next;
	}

	@Override
	public boolean prepare(final AutoDriveSustainer entity) {
		KeywordExample example = new KeywordExample();
		example.setUserId(entity.getAdgroup().getUserID());
		example.createCriteria().andAdgroupIdEqualTo(entity.getAdgroup().getAdgroupID());
		entity.setKeywords(this.keywordMapper.selectByExample(example));
		
		if(entity.getKeywords().size() == 0){
			return false;
		}
		
		Date ago4 = DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -4);
		Date yesterday = DateUtils.addDays(DateUtils.truncate(new Date(), Calendar.DATE), -1);
		List<String> words = new ArrayList<String>();
		for(Keyword kw: entity.getKeywords()){
			words.add(kw.getWord());
		}
		List<String> toRunWords = new ArrayList<String>();
		while(words.size() > 0){
			toRunWords = words.size()> 70?words.subList(0, 70):words;
			if(words.size()> 70){
				toRunWords = words.subList(0, 70);
				words = words.subList(70, words.size());
			}else{
				toRunWords = words.subList(0, words.size());
				words = new ArrayList<String>();
			}
			GlobalWordRespone res = this.simbaProxyClient.findGlobalData(toRunWords, DateFormatUtils.format(ago4, "yyyy-MM-dd"), 
					DateFormatUtils.format(yesterday, "yyyy-MM-dd"), entity.getAdgroup().getUserID());
			if(res != null && res.getRes() != null && res.getRes().getDataList() != null
					&& res.getRes().getDataList().getWords() != null){
				entity.getInsightDatas().addAll(res.getRes().getDataList().getWords());
			}
			
			if(log.isDebugEnabled()){
				log.debug("last 3 days' insight datas: {}", entity.getInsightDatas() != null?-1:entity.getInsightDatas().size());
			}
		}
		
		Date today = DateUtils.truncate(new Date(), Calendar.DATE);
		RptKeywordExample ex = new RptKeywordExample();
		ex.setUserId(entity.getAdgroup().getUserID());
		ex.createCriteria().andAdgroupIdEqualTo(entity.getAdgroup().getAdgroupID()).andDateBetween(DateUtils.addDays(today, -7), today);
		entity.setLast7daysReport(this.rptKeywordMapper.selectSumByExample(ex));
		
		if(log.isDebugEnabled()){
			log.debug("last 7 days' states: {}", entity.getLast7daysReport() != null?-1:entity.getLast7daysReport().size());
		}
		
		ex = new RptKeywordExample();
		ex.setUserId(entity.getAdgroup().getUserID());
		ex.createCriteria().andAdgroupIdEqualTo(entity.getAdgroup().getAdgroupID()).andDateBetween(DateUtils.addDays(today, -3), today);
		entity.setLast3daysReport(this.rptKeywordMapper.selectSumByExample(ex));
		if(log.isDebugEnabled()){
			log.debug("last 3 days' states: {}", entity.getLast3daysReport() != null?-1:entity.getLast3daysReport().size());
		}
		
		return entity.getQueue() != null && entity.getKeywords() != null && entity.getKeywords().size() > 0 
				&& entity.getLast3daysReport() != null && entity.getLast3daysReport().size() > 0;
	}

	@Override
	public SustainRule.SustainStatus sustain(AutoDriveSustainer entity) {
		SinbadClient.AdgroupDto dto = new SinbadClient.AdgroupDto();
		dto.setStatus(1);
		this.sinbadClient.updateAdgroupStatus(entity.getAdgroup().getAdgroupID(), dto);
		
		return SustainRule.SustainStatus.pricing;
	}

	@Override
	public boolean maintain(AutoDriveSustainer entity,
			SustainRule.SustainStatus status) {
		AutoDriveActionLog actionLog = new AutoDriveActionLog();
		actionLog.from(entity.getQueue(), entity.getAdgroup());
		actionLog.setupID(entity.getAdgroup().getAdgroupID(), new Date());
		actionLog.setExecuteTime(new Date());
		for(Keyword kw: entity.getKeywords()){
			actionLog.addKeyword(kw);
		}
		
		List<Keyword> pricingKeywords = new ArrayList<Keyword>();
		List<Keyword> removingKeywords = new ArrayList<Keyword>();
		Map<String, Keyword> existingKeywords = new HashMap<String, Keyword>();
		for(Keyword kw: entity.getKeywords()){
			existingKeywords.put(kw.getWord(), kw);
			AutoDriveKeywordSustainer kwSustainer = new AutoDriveKeywordSustainer();
			kwSustainer.setKeyword(kw);
			kwSustainer.setAdgroup(entity.getAdgroup());
			kwSustainer.setQueue(entity.getQueue());
			kwSustainer.setLast3daysReport(entity.findLast3daysReport(kw.getKeywordId()));
			kwSustainer.setLast7daysReport(entity.findLast7daysReport(kw.getKeywordId()));
			kwSustainer.setLast3InsightData(entity.findInsightDta(kw.getWord()));
			
			boolean toNext = this.autoDriveKeywordSustainRule.check(kwSustainer);
			if(toNext){
				toNext = this.autoDriveKeywordSustainRule.prepare(kwSustainer);
				if(toNext){
					SustainRule.SustainStatus ss = this.autoDriveKeywordSustainRule.sustain(kwSustainer);
					if(SustainRule.SustainStatus.pricing == ss){
						pricingKeywords.add(kw);
						AutoDriveActionLog.KeywordSnapshot ks = actionLog.findByKeywordID(kw.getKeywordId());
						if(ks != null){
							ks.setNewMaxPrice(kwSustainer.getAdjustPrice());
						}
					}else if(SustainRule.SustainStatus.remove == ss){
						removingKeywords.add(kw);
					}
					this.autoDriveKeywordSustainRule.mark(kwSustainer);
				}
			}
		}
		
		//调价
		if(pricingKeywords.size() > 0){
			List<SinbadClient.PricingKeyword> handlings = new ArrayList<SinbadClient.PricingKeyword>();
			for(Keyword kw: pricingKeywords){
				SinbadClient.PricingKeyword hkw = new SinbadClient.PricingKeyword();
				hkw.setKeywordID(kw.getKeywordId());
				hkw.setIsDefaultPrice(0);
				hkw.setMatchScope(kw.getMatchscope());
				hkw.setWord(kw.getWord());
				hkw.setMaxPrice(kw.getMaxPrice());
				if(hkw.getMaxPrice() > 0){
					handlings.add(hkw);
					log.debug("to pricing keyword: {}", kw);
					
					AutoDriveActionLog.KeywordSnapshot ks = actionLog.findByKeywordID(hkw.getKeywordID());
					if(ks != null){
						ks.setNewMaxPrice(hkw.getMaxPrice());
					}
					
				}else{
					log.debug("not pricing keyword as zero: {}", kw);
				}
			}
			if(handlings.size() > 0){
				SinbadClient.ListResponse<SinbadClient.PricingKeyword> ret = this.sinbadClient.pricingKeywords(entity.getAdgroup().getUserID(), handlings);
				log.debug("price keywords ret: {}", ret);
				if(ret.getDatas() != null && ret.getDatas().size() > 0){
					if(handlings.size() != ret.getDatas().size()){
						log.error("some keywords fail to priced: {}", entity.getAdgroup());
					}
					for(SinbadClient.PricingKeyword pk:ret.getDatas()){
						AutoDriveActionLog.KeywordSnapshot ks = actionLog.findByKeywordID(pk.getKeywordID());
						if(ks != null){
							ks.setResult(true);
						}
					}
				}else{
					log.error("no keyword is success to priced: {}", entity.getAdgroup());
				}
				entity.setPriced(true);
			}
		}
		
		//删词
		if(removingKeywords.size() > 0){
			List<Long> ids = new ArrayList<Long>();
			for(Keyword kw: removingKeywords){
				ids.add(kw.getKeywordId());
				existingKeywords.remove(kw.getWord());
				log.debug("to delete keyword: {}", kw);
				AutoDriveActionLog.KeywordSnapshot ks = actionLog.findByKeywordID(kw.getKeywordId());
				if(ks != null){
					ks.setWillBeDeleted(true);
				}
			}
			SinbadClient.ListResponse<SinbadClient.PricingKeyword> ret = this.sinbadClient.delKeywords(entity.getAdgroup().getCampaignID(), ids);
			log.debug("delete keywords ret: {}", ret);
			if(ret.getDatas() != null && ret.getDatas().size() > 0){
				if(removingKeywords.size() != ret.getDatas().size()){
					log.error("some keywords fail to deleted: {}", entity.getAdgroup());
				}
				for(SinbadClient.PricingKeyword pk:ret.getDatas()){
					AutoDriveActionLog.KeywordSnapshot ks = actionLog.findByKeywordID(pk.getKeywordID());
					if(ks != null){
						ks.setResult(true);
					}
				}
			}else{
				log.error("no keyword is success to deleted: {}", entity.getAdgroup());
			}
			entity.setRemoved(true);
		}
		
		//补词
		int valve = 50;
		if(this.chooseHandlers != null){
			ChooseHandler.ChooseHandleContext<Adgroup> context = new ChooseHandler.ChooseHandleContext<Adgroup>();
			context.setEntity(entity.getAdgroup());
			for(ChooseHandler<Adgroup> handler:this.chooseHandlers){
				if(handler.support(context)){
					valve = handler.count(context);
					break;
				}
			}
		}
		valve = Math.max(valve, 1);
		if(existingKeywords.size() < valve){
			log.debug("need more {} keywords: {}", (valve - existingKeywords.size()), entity.getAdgroup());
			List<LexiconClient.RecommendWordDto> recommendWords = this.lexiconClient.smartWordsAsDevice(entity.getAdgroup().getAdgroupID(),
					AutoDriverUtils.covert2mode(entity.getQueue().getType()), 0, entity.getAdgroup().getDefaultPrice(), 
					((float) entity.getQueue().getMaxPrice()) / 100);
			if (recommendWords == null || recommendWords.size() == 0) {
				log.warn("no recommend words: {}", entity.getAdgroup());
				return true;
			}
			
			List<SinbadClient.HandlingKeyword> handlingWords = null;
			handlingWords = this.recommendWordFilter.filter(recommendWords, entity.getQueue(), entity.getAdgroup());
			if(handlingWords == null){
				log.warn("no appropriate recommend words: {}", entity.getAdgroup());
				return true;
			}
			
			List<SinbadClient.HandlingKeyword> toAddingWords = new ArrayList<SinbadClient.HandlingKeyword>();
			for(SinbadClient.HandlingKeyword hk: handlingWords){
				if(!existingKeywords.containsKey(hk.getWord())){
					toAddingWords.add(hk);
				}
			}
			int remaining = valve - existingKeywords.size();
			if(toAddingWords.size() > remaining){
				toAddingWords = toAddingWords.subList(0, remaining);
			}
			
			if(log.isDebugEnabled()){
				for(SinbadClient.HandlingKeyword keyword: toAddingWords){
					log.debug("to add word: {}", keyword);
				}
			}
			if(toAddingWords.size() > 0){
				SinbadClient.ListResponse<SinbadClient.PricingKeyword> ret = this.sinbadClient.addKeywords(entity.getAdgroup().getAdgroupID(), handlingWords);
				log.debug("add keywords ret: {}", ret);
				if(ret.getDatas() != null && ret.getDatas().size() > 0){
					if(handlingWords.size() != ret.getDatas().size()){
						log.error("some keywords fail to added: {}", entity.getAdgroup());
					}
					for(SinbadClient.HandlingKeyword hk:toAddingWords){
						SinbadClient.PricingKeyword addedKeyword = this.filterByWord(ret.getDatas(), hk.getWord());
						if(addedKeyword != null){
							hk.setKeywordID(addedKeyword.getKeywordID());
							hk.setMatchScope(addedKeyword.getMatchScope());
							hk.setMaxPrice(addedKeyword.getMaxPrice());
						}
					}
				}else{
					log.error("no keyword is success to added: {}", entity.getAdgroup());
				}
				actionLog.setAddKeywords(toAddingWords);
			}else{
				log.error("finally, no recommend words to add: {}", entity.getAdgroup());
			}
			entity.setAdded(true);
		}
		
		this.autoDriveActionLogRepository.save(actionLog);
		
		return true;
	}
	
	private SinbadClient.PricingKeyword filterByWord(List<SinbadClient.PricingKeyword> keywords, String word){
		for(SinbadClient.PricingKeyword pw:keywords){
			if(pw.getWord() != null && pw.getWord().equals(word)){
				return pw;
			}
		}
		return null;
	}

	@Override
	public boolean mark(AutoDriveSustainer entity) {
		SinbadClient.AdgroupDto dto = new SinbadClient.AdgroupDto();
		dto.setStatus(0);
		dto.setDriveType(-1);
		if(entity.isAdded() || entity.isPriced() || entity.isRemoved()){
			dto.setAdjustCount(entity.getAdgroup().getAdjustCount() + 1);
		}
		dto.setMaintainedDate(new Date());
		this.sinbadClient.updateAdgroup(entity.getAdgroup().getAdgroupID(), dto);
		return false;
	}

}
